package com.huaxonline.boot.log;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.ServletUtil;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Parameter;
import java.text.MessageFormat;
import java.time.Instant;
import java.util.*;
import java.util.regex.Pattern;

/**
 * 日志切面 - 简化版
 * 
 * @author zhaoshulei
 * @since 2024-05-22
 */
@Component
@Aspect
@Slf4j
public class WebLogAopAspect {

    private final LogProperties logProperties;
    private final AntPathMatcher pathMatcher = new AntPathMatcher();

    public WebLogAopAspect(LogProperties logProperties) {
        this.logProperties = logProperties;
    }

    /**
     * 精确的切点定义 - 只拦截业务Controller
     */
    @Pointcut("@annotation(com.huaxonline.boot.log.WebLogAnnotation)")
    public void webLogAnnotation() {
    }

    /**
     * 业务Controller切点 - 根据包路径和类名过滤
     */
    @Pointcut("(@annotation(org.springframework.web.bind.annotation.RequestMapping) || " +
              "@annotation(org.springframework.web.bind.annotation.GetMapping) || " +
              "@annotation(org.springframework.web.bind.annotation.PostMapping) || " +
              "@annotation(org.springframework.web.bind.annotation.PutMapping) || " +
              "@annotation(org.springframework.web.bind.annotation.DeleteMapping) || " +
              "@annotation(org.springframework.web.bind.annotation.PatchMapping)) && " +
              "execution(* com.huaxonline.boot..controller..*(..))")
    public void businessControllerMapping() {
    }

    /**
     * 最终切点 - 结合注解和业务Controller
     */
    @Pointcut("webLogAnnotation() || businessControllerMapping()")
    public void logPointcut() {
    }

    @Around("logPointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        // 获取基本信息
        Class<?> targetClass = joinPoint.getTarget().getClass();
        String methodName = joinPoint.getSignature().getName();
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        WebLogAnnotation annotation = methodSignature.getMethod().getAnnotation(WebLogAnnotation.class);
        
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String requestURL = request.getRequestURL().toString();
        String requestURI = request.getRequestURI();
        String ip = ServletUtil.getClientIP(request);
        long start = System.currentTimeMillis();
        
        // 1. 全局开关检查
        if (logProperties == null || !logProperties.isEnabled()) {
            return joinPoint.proceed();
        }
        
        // 2. 精确过滤检查
        if (!shouldLogRequest(targetClass, requestURI, annotation)) {
            return joinPoint.proceed();
        }
        
        // 3. 采样与忽略判断（注解优先，其次全局采样率）
        if (annotation != null && annotation.ignore()) {
            return joinPoint.proceed();
        }
        double rate = (annotation != null ? annotation.sampleRate() : 1.0D);
        if (rate >= 1.0D) {
            rate = logProperties.getSampleRate();
        }
        rate = Math.max(0.0D, Math.min(1.0D, rate));
        if (rate < 1.0D && Math.random() > rate) {
            return joinPoint.proceed();
        }

        // 4. 构建日志对象
        WebLog webLog = new WebLog();
        String description = resolveDescription(methodSignature, annotation, request);
        webLog.setDescription(description);
        webLog.setSourceId(request.getHeader("AppId"));
        webLog.setUrl(requestURL);
        webLog.setUri(requestURI);
        webLog.setHeader(getHeader(request));
        webLog.setIp(ip);
        webLog.setClasses(targetClass.getName());
        webLog.setMethod(methodSignature.getMethod().getName());
        
        // 5. 根据配置决定是否记录请求参数
        Object requestParams = null;
        String requestJson = null;
        if (logProperties.isLogRequestParams()) {
            requestParams = getRequestParams(joinPoint, request);
            GsonBuilder gsonBuilder = new GsonBuilder();
            gsonBuilder.setFieldNamingStrategy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);
            Gson gson = gsonBuilder.create();
            requestJson = ObjectUtil.isNull(requestParams) ? null : gson.toJson(requestParams);
            requestJson = maskJsonIfNeeded(requestJson, annotation);
            requestJson = maskJsonByGlobal(requestJson);
        }
        webLog.setRequestParam(requestJson);
        webLog.setStartTime(DateUtil.toLocalDateTime(Instant.ofEpochMilli(start)));
        
        // 6. 打印请求信息
        log.info("\n【接口请求信息】\n请求ip:{}\n请求来源:{}\n请求接口:{}\n请求接口地址:{}\n请求类:{}\n请求方法:{}\n请求参数:{}\n请求header:{}\n请求时间:{}", 
                ip, webLog.getSourceId(), description, requestURL, targetClass.getSimpleName(), methodName, 
                webLog.getRequestParam(), webLog.getHeader(), webLog.getStartTime());
        
        // 7. 执行目标方法并记录结果
        Object result = null;
        try {
            result = joinPoint.proceed();
            
            // 根据配置决定是否记录响应结果
            if (logProperties.isLogResponseResult()) {
                GsonBuilder gsonBuilder = new GsonBuilder();
                gsonBuilder.setFieldNamingStrategy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);
                Gson gson = gsonBuilder.create();
                String respJson = ObjectUtil.isNull(result) ? null : gson.toJson(result);
                respJson = maskJsonIfNeeded(respJson, annotation);
                respJson = maskJsonByGlobal(respJson);
                webLog.setResult(respJson);
            }
            
        } catch (Exception e) {
            if (logProperties.isLogExceptionStack()) {
                webLog.setErrorMsg(ExceptionUtil.stacktraceToString(e));
            } else {
                webLog.setErrorMsg(ExceptionUtil.getMessage(e));
            }
            throw e;
        } finally {
            long spendTime = System.currentTimeMillis() - start;
            webLog.setSpendTime(spendTime);
            
            // 8. 根据响应时间阈值决定是否记录日志
            if (spendTime >= logProperties.getMinResponseTimeThreshold()) {
                log.info("\n【接口返回信息】\n请求接口:{}\n请求接口地址:{}\n方法:{}\n请求时间:{}\n处理时间:{} 毫秒\n返回结果:{}\n异常信息:{}", 
                        description, requestURL, methodName, webLog.getStartTime(), 
                        webLog.getSpendTime(), webLog.getResult(), webLog.getErrorMsg());
            }
        }
        return result;
    }

    /**
     * 判断是否应该记录请求日志
     */
    private boolean shouldLogRequest(Class<?> targetClass, String requestURI, WebLogAnnotation annotation) {
        // 1. 如果有@WebLogAnnotation注解，强制记录
        if (annotation != null) {
            return true;
        }
        
        // 2. 检查是否只记录业务Controller
        if (logProperties.isOnlyBusinessControllers()) {
            if (!isBusinessController(targetClass)) {
                return false;
            }
        }
        
        // 3. 检查排除的URL模式
        if (isExcludedUrl(requestURI)) {
            return false;
        }
        
        // 4. 检查排除的Controller模式
        if (isExcludedController(targetClass)) {
            return false;
        }
        
        return true;
    }
    
    /**
     * 判断是否为业务Controller
     */
    private boolean isBusinessController(Class<?> targetClass) {
        String className = targetClass.getName();
        Set<String> businessPackages = logProperties.getBusinessPackagesSet();
        
        for (String packageName : businessPackages) {
            if (className.startsWith(packageName)) {
                return true;
            }
        }
        return false;
    }
    
    /**
     * 判断URL是否在排除列表中
     */
    private boolean isExcludedUrl(String requestURI) {
        Set<String> excludePatterns = logProperties.getExcludeUrlPatternsSet();
        
        for (String pattern : excludePatterns) {
            if (pathMatcher.match(pattern, requestURI)) {
                return true;
            }
        }
        return false;
    }
    
    /**
     * 判断Controller是否在排除列表中
     */
    private boolean isExcludedController(Class<?> targetClass) {
        String className = targetClass.getSimpleName();
        Set<String> excludePatterns = logProperties.getExcludeControllerPatternsSet();
        
        for (String pattern : excludePatterns) {
            if (pathMatcher.match(pattern, className)) {
                return true;
            }
        }
        return false;
    }

    private String getHeader(HttpServletRequest request) {
        StringBuffer headerBuf = new StringBuffer();
        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String key = headerNames.nextElement();
            String value = request.getHeader(key);
            if (headerBuf.length() > 0) {
                headerBuf.append("|");
            }
            headerBuf.append(key).append("=").append(value);
        }
        return headerBuf.toString();
    }

    private Object getRequestParams(ProceedingJoinPoint joinPoint, HttpServletRequest request) {
        try {
            Object[] args = joinPoint.getArgs();
            String method = request.getMethod();
            String contentType = request.getContentType();
            
            // 构建参数Map，包含所有类型的参数
            Map<String, Object> allParams = new HashMap<>();
            
            // 1. 处理查询参数（GET请求的URL参数）
            Map<String, String[]> queryParams = request.getParameterMap();
            if (MapUtil.isNotEmpty(queryParams)) {
                queryParams.forEach((key, values) -> {
                    if (values.length == 1) {
                        allParams.put("query_" + key, values[0]);
                    } else {
                        allParams.put("query_" + key, Arrays.asList(values));
                    }
                });
            }
            
            // 2. 处理方法参数
            if (args.length > 0) {
                MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
                Parameter[] parameters = methodSignature.getMethod().getParameters();
                
                for (int i = 0; i < args.length; i++) {
                    Object arg = args[i];
                    Parameter param = parameters[i];
                    
                    // 跳过HttpServletRequest、HttpServletResponse等框架参数
                    if (isFrameworkParameter(arg)) {
                        continue;
                    }
                    
                    // 处理@RequestBody参数（JSON请求体）
                    if (param.isAnnotationPresent(org.springframework.web.bind.annotation.RequestBody.class)) {
                        allParams.put("requestBody", arg);
                        continue;
                    }
                    
                    // 处理@PathVariable参数
                    if (param.isAnnotationPresent(org.springframework.web.bind.annotation.PathVariable.class)) {
                        org.springframework.web.bind.annotation.PathVariable pathVar = 
                            param.getAnnotation(org.springframework.web.bind.annotation.PathVariable.class);
                        String paramName = StrUtil.isNotBlank(pathVar.value()) ? pathVar.value() : param.getName();
                        allParams.put("path_" + paramName, arg);
                        continue;
                    }
                    
                    // 处理@RequestParam参数
                    if (param.isAnnotationPresent(org.springframework.web.bind.annotation.RequestParam.class)) {
                        org.springframework.web.bind.annotation.RequestParam requestParam = 
                            param.getAnnotation(org.springframework.web.bind.annotation.RequestParam.class);
                        String paramName = StrUtil.isNotBlank(requestParam.value()) ? requestParam.value() : param.getName();
                        allParams.put("param_" + paramName, arg);
                        continue;
                    }
                    
                    // 处理MultipartFile文件上传
                    if (arg instanceof MultipartFile) {
                        MultipartFile file = (MultipartFile) arg;
                        String fileInfo = MessageFormat.format("文件名: {0}, 大小: {1} bytes, 类型: {2}", 
                            file.getOriginalFilename(), file.getSize(), file.getContentType());
                        allParams.put("file_" + param.getName(), fileInfo);
                        continue;
                    }
                    
                    // 处理MultipartFile数组
                    if (arg instanceof MultipartFile[]) {
                        MultipartFile[] files = (MultipartFile[]) arg;
                        List<String> fileInfos = new ArrayList<>();
                        for (MultipartFile file : files) {
                            String fileInfo = MessageFormat.format("文件名: {0}, 大小: {1} bytes, 类型: {2}", 
                                file.getOriginalFilename(), file.getSize(), file.getContentType());
                            fileInfos.add(fileInfo);
                        }
                        allParams.put("files_" + param.getName(), fileInfos);
                        continue;
                    }
                    
                    // 处理其他类型参数（根据Content-Type判断）
                    if (isJsonRequest(contentType)) {
                        allParams.put("jsonParam_" + param.getName(), arg);
                    } else if (isFormRequest(contentType)) {
                        allParams.put("formParam_" + param.getName(), arg);
                    } else {
                        allParams.put("param_" + param.getName(), arg);
                    }
                }
            }
            
            // 3. 根据请求方法特殊处理
            if (ServletUtil.METHOD_GET.equals(method)) {
                // GET请求主要关注查询参数和路径参数
                return buildGetRequestParams(allParams);
            } else if (ServletUtil.METHOD_POST.equals(method) || ServletUtil.METHOD_PUT.equals(method) || 
                      HttpMethod.PATCH.matches(method)) {
                // POST/PUT/PATCH请求关注请求体参数
                return buildPostRequestParams(allParams, contentType);
            } else if (ServletUtil.METHOD_DELETE.equals(method)) {
                // DELETE请求可能包含查询参数和路径参数
                return buildDeleteRequestParams(allParams);
            }
            
            return allParams.isEmpty() ? null : allParams;
            
        } catch (Exception e) {
            log.warn("获取请求参数失败: {}", e.getMessage(), e);
            return "参数获取失败: " + e.getMessage();
        }
    }
    
    /**
     * 判断是否为框架参数
     */
    private boolean isFrameworkParameter(Object arg) {
        return arg instanceof HttpServletRequest || 
               arg instanceof javax.servlet.http.HttpServletResponse ||
               arg instanceof org.springframework.web.multipart.MultipartHttpServletRequest ||
               arg instanceof org.springframework.ui.Model ||
               arg instanceof org.springframework.ui.ModelMap ||
               arg instanceof org.springframework.validation.BindingResult ||
               arg instanceof org.springframework.web.servlet.mvc.support.RedirectAttributes;
    }
    
    /**
     * 判断是否为JSON请求
     */
    private boolean isJsonRequest(String contentType) {
        return StrUtil.isNotBlank(contentType) && 
               (contentType.contains("application/json") || contentType.contains("application/vnd.api+json"));
    }
    
    /**
     * 判断是否为表单请求
     */
    private boolean isFormRequest(String contentType) {
        return StrUtil.isNotBlank(contentType) && 
               (contentType.contains("application/x-www-form-urlencoded") || 
                contentType.contains("multipart/form-data"));
    }
    
    /**
     * 构建GET请求参数
     */
    private Object buildGetRequestParams(Map<String, Object> allParams) {
        if (allParams.isEmpty()) {
            return null;
        }
        
        // 分离查询参数和路径参数
        Map<String, Object> queryParams = new HashMap<>();
        Map<String, Object> pathParams = new HashMap<>();
        
        allParams.forEach((key, value) -> {
            if (key.startsWith("query_")) {
                queryParams.put(key.substring(6), value);
            } else if (key.startsWith("path_")) {
                pathParams.put(key.substring(5), value);
            }
        });
        
        Map<String, Object> result = new HashMap<>();
        if (!queryParams.isEmpty()) {
            result.put("queryParams", queryParams);
        }
        if (!pathParams.isEmpty()) {
            result.put("pathParams", pathParams);
        }
        
        return result.isEmpty() ? null : result;
    }
    
    /**
     * 构建POST/PUT/PATCH请求参数
     */
    private Object buildPostRequestParams(Map<String, Object> allParams, String contentType) {
        if (allParams.isEmpty()) {
            return null;
        }
        
        // 如果有requestBody，优先返回
        if (allParams.containsKey("requestBody")) {
            return allParams.get("requestBody");
        }
        
        // 分离不同类型的参数
        Map<String, Object> formParams = new HashMap<>();
        Map<String, Object> fileParams = new HashMap<>();
        Map<String, Object> otherParams = new HashMap<>();
        
        allParams.forEach((key, value) -> {
            if (key.startsWith("file_") || key.startsWith("files_")) {
                fileParams.put(key, value);
            } else if (key.startsWith("formParam_") || key.startsWith("param_")) {
                String paramName = key.startsWith("formParam_") ? key.substring(10) : key.substring(6);
                formParams.put(paramName, value);
            } else if (!key.startsWith("query_") && !key.startsWith("path_")) {
                otherParams.put(key, value);
            }
        });
        
        Map<String, Object> result = new HashMap<>();
        if (!formParams.isEmpty()) {
            result.put("formParams", formParams);
        }
        if (!fileParams.isEmpty()) {
            result.put("fileParams", fileParams);
        }
        if (!otherParams.isEmpty()) {
            result.put("otherParams", otherParams);
        }
        
        return result.isEmpty() ? null : result;
    }
    
    /**
     * 构建DELETE请求参数
     */
    private Object buildDeleteRequestParams(Map<String, Object> allParams) {
        if (allParams.isEmpty()) {
            return null;
        }
        
        Map<String, Object> result = new HashMap<>();
        Map<String, Object> queryParams = new HashMap<>();
        Map<String, Object> pathParams = new HashMap<>();
        
        allParams.forEach((key, value) -> {
            if (key.startsWith("query_")) {
                queryParams.put(key.substring(6), value);
            } else if (key.startsWith("path_")) {
                pathParams.put(key.substring(5), value);
            } else {
                result.put(key, value);
            }
        });
        
        if (!queryParams.isEmpty()) {
            result.put("queryParams", queryParams);
        }
        if (!pathParams.isEmpty()) {
            result.put("pathParams", pathParams);
        }
        
        return result.isEmpty() ? null : result;
    }

    private String resolveDescription(MethodSignature methodSignature, WebLogAnnotation annotation, HttpServletRequest request) {
        // 1) 注解 value 优先
        if (annotation != null && StrUtil.isNotBlank(annotation.value())) {
            return buildWithCategoryEvent(annotation.value(), annotation);
        }
        // 2) 反射获取 @Operation(summary)
        try {
            Object op = methodSignature.getMethod().getAnnotation((Class) Class.forName("io.swagger.v3.oas.annotations.Operation"));
            if (op != null) {
                String summary = (String) op.getClass().getMethod("summary").invoke(op);
                if (StrUtil.isNotBlank(summary)) {
                    return buildWithCategoryEvent(summary, annotation);
                }
            }
        } catch (Throwable ignore) {
            // no-op
        }
        // 3) 方法签名或URI兜底
        String methodName = methodSignature.getMethod().getName();
        return buildWithCategoryEvent(methodName, annotation);
    }

    private String buildWithCategoryEvent(String base, WebLogAnnotation annotation) {
        if (annotation == null) {
            return base;
        }
        String category = StrUtil.emptyIfNull(annotation.category());
        String event = StrUtil.emptyIfNull(annotation.event());
        if (StrUtil.isAllBlank(category, event)) {
            return base;
        }
        if (StrUtil.isNotBlank(category) && StrUtil.isNotBlank(event)) {
            return "[" + category + "] " + event + " - " + base;
        }
        if (StrUtil.isNotBlank(category)) {
            return "[" + category + "] " + base;
        }
        return event + " - " + base;
    }

    private String maskJsonIfNeeded(String json, WebLogAnnotation annotation) {
        if (StrUtil.isBlank(json) || annotation == null || annotation.maskFields().length == 0) {
            return json;
        }
        String masked = json;
        for (String field : annotation.maskFields()) {
            if (StrUtil.isBlank(field)) continue;
            // 简单掩码：匹配 "field":"任意" 或 "field":任意数字/布尔/null
            String key = Pattern.quote(field.trim());
            masked = masked.replaceAll("(\\\"" + key + "\\\"\\s*:\\s*\\\")([^\\\"]*)(\\\")", "$1***$3");
            masked = masked.replaceAll("(\\\"" + key + "\\\"\\s*:\\s*)(-?\\d+(?:\\.\\d+)?)(\n|,|}| )", "$1***$3");
            masked = masked.replaceAll("(\\\"" + key + "\\\"\\s*:\\s*)(true|false|null)(\n|,|}| )", "$1***$3");
        }
        return masked;
    }

    private String maskJsonByGlobal(String json) {
        if (StrUtil.isBlank(json) || logProperties == null || logProperties.getMaskFields() == null || logProperties.getMaskFields().length == 0) {
            return json;
        }
        String masked = json;
        for (String field : logProperties.getMaskFields()) {
            if (StrUtil.isBlank(field)) continue;
            String key = Pattern.quote(field.trim());
            masked = masked.replaceAll("(\\\"" + key + "\\\"\\s*:\\s*\\\")([^\\\"]*)(\\\")", "$1" + logProperties.getMaskReplacement() + "$3");
            masked = masked.replaceAll("(\\\"" + key + "\\\"\\s*:\\s*)(-?\\d+(?:\\.\\d+)?)(\n|,|}| )", "$1" + logProperties.getMaskReplacement() + "$3");
            masked = masked.replaceAll("(\\\"" + key + "\\\"\\s*:\\s*)(true|false|null)(\n|,|}| )", "$1" + logProperties.getMaskReplacement() + "$3");
        }
        return masked;
    }

    /**
     * 获取脱敏字段的Set集合
     */
    public Set<String> getMaskFieldsSet() {
        return logProperties.getMaskFieldsSet();
    }
}
